﻿namespace GameHelper.Saving.Editor
{
    using System;
    using System.IO;
    using System.Text;
    using System.Reflection;
    using UnityEngine;
    using UnityEditor;
    using System.Collections.Generic;
    // using System.Linq;

    public class CodeGenerator
    {
        public class SaveKey
        {
            public string keyName;
            public Type keyType;
            public string keyValue;
            /// <summary>
            /// 键值容器类型，例如class或者Enum
            /// </summary>
            public Type containerType;
            public FieldInfo keyFieldInfo;

            public SaveAttribute saveAttr;

            public SaveKey() { }
        }

        public class ScriptInfo
        {
            public CodeGenAttribute codeGenAttr;

            public List<SaveKey> saveKeys;

            public ScriptInfo(CodeGenAttribute codeGenAttr)
            {
                this.codeGenAttr = codeGenAttr;
                saveKeys = new List<SaveKey>();
            }

            /// <summary>
            /// 生成代码
            /// </summary>
            /// <returns></returns>
            public string GetCode(CodeGenerator cg)
            {
                var fileTemplate = cg.GetFileTemplate(this);
                fileTemplate = fileTemplate.Replace("#NAMESPACE#", codeGenAttr.typeNamespace);
                fileTemplate = fileTemplate.Replace("#TYPE_NAME#", codeGenAttr.typeName);
                fileTemplate = fileTemplate.Replace("#USING#", "");
                // fileTemplate = fileTemplate.Replace("#ALL_KEYS#", "");
                if (codeGenAttr.isPartialType)
                {
                    fileTemplate = fileTemplate.Replace("#PARTIAL#", "partial");
                }
                else
                {
                    fileTemplate = fileTemplate.Replace("#PARTIAL#", "");
                }
                var keysBuff = new List<string>();
                var keys = new List<SaveKey>(saveKeys);
                // 按键名称排序，减少多人协作时代码冲突
                keys.Sort((a, b) => a.keyName.CompareTo(b.keyName));
                var sourceCode = new List<string>();
                var newApiClsDef = new List<string>();
                var newApiValueDef = new List<string>();
                foreach (var k in keys)
                {
                    var saveValueType = k.saveAttr.valueType;
                    var valueTypeName = CSharpUtil.GetTypeShortName(saveValueType);
                    var env = new Dictionary<string, string>() {
                        {"#NAME#", k.keyName},
                        {"#VALUE_TYPE#", valueTypeName},
                        {"#KEY#", $"\"{k.keyValue}\""}
                    };
                    // keysBuff.Add($"\"{k.keyName}\"");
                    var keyCodeIndent = new string(' ', 12);
                    if (string.IsNullOrWhiteSpace(k.saveAttr.comment))
                    {
                        keysBuff.Add($"{keyCodeIndent}\"{k.keyValue}\", // {valueTypeName}");
                    }
                    else
                    {
                        keysBuff.Add($"{keyCodeIndent}\"{k.keyValue}\", // {valueTypeName} // {k.saveAttr.comment.TrimEnd()}");
                    }
                    string saveTemplate = string.Empty;
                    var prefixMode = null != k.saveAttr.suffixType;
                    var clsTmpl = "";
                    if (prefixMode)
                    {
                        if (k.saveAttr.useCache)
                        {
                            saveTemplate = cg.GetPrefixedCachedSaveTemplate(this);
                            clsTmpl = cg.GetCodeTemplate("ValuePrefixCachedClass");
                        }
                        else
                        {
                            saveTemplate = cg.GetPrefixedRawSaveTemplate(this);
                            clsTmpl = cg.GetCodeTemplate("ValuePrefixClass");
                        }
                        env["#SUFFIX_TYPE#"] = CSharpUtil.GetTypeShortName(k.saveAttr.suffixType);
                        if (typeof(string) == k.saveAttr.suffixType)
                        {
                            env["#KEY#"] = $"\"{k.keyValue}\" + \"-\" + suffix";
                        }
                        else
                        {
                            env["#KEY#"] = $"\"{k.keyValue}\" + \"-\" + suffix.ToString()";
                        }
                    }
                    else
                    {
                        if (k.saveAttr.useCache)
                        {
                            saveTemplate = cg.GetCachedSaveTemplate(this);
                            clsTmpl = cg.GetCodeTemplate("ValueCachedClass");
                        }
                        else
                        {
                            saveTemplate = cg.GetRawSaveTemplate(this);
                            clsTmpl = cg.GetCodeTemplate("ValueClass");
                        }
                    }
                    if (string.IsNullOrEmpty(saveTemplate))
                    {
                        continue;
                    }
                    foreach (var e in env)
                    {
                        saveTemplate = saveTemplate.Replace(e.Key, e.Value);
                    }
                    var codeLines = saveTemplate.SplitCodeLines();
                    codeLines.CodeIndent(2);
                    if (string.IsNullOrWhiteSpace(k.saveAttr.comment))
                    {
                        var indent = new string(' ', 8);
                        codeLines.ReplaceInLines($"{indent}#COMMENT#", "");
                    }
                    else
                    {
                        var indent = new string(' ', 8);
                        codeLines.ReplaceInLines("#COMMENT#", $"/// <summary>\n{indent}/// {k.saveAttr.comment.TrimEnd()}\n{indent}/// </summary>");
                    }
                    sourceCode.AddRange(codeLines);

                    do
                    {
                        // var clsTmpl = cg.GetCodeTemplate("ValueClass");
                        env["#CLS_NAME#"] = $"Cls_{k.keyName}";
                        foreach (var e in env)
                        {
                            clsTmpl = clsTmpl.Replace(e.Key, e.Value);
                        }
                        var clsLines = clsTmpl.SplitCodeLinesToList();
                        clsLines.CodeIndent(2);
                        newApiClsDef.AddRange(clsLines);

                        newApiValueDef.Add($"public Cls_{k.keyName} {k.keyName};");
                    } while (false);
                }
                // var keysLines = keysBuff
                //     .Select((x, i) => new { Index = i, Value = x })
                //     .GroupBy(x => x.Index / 4)
                //     .Select(x => x.Select(v => v.Value).ToList())
                //     .ToList();
                // var keysCode = new StringBuilder();
                // foreach (var line in keysLines)
                // {
                //     keysCode.Append(new string(' ', 12));
                //     keysCode.Append(string.Join(", ", line));
                //     keysCode.Append(",\n");
                // }
                // if (keysCode.Length > 0)
                // {
                //     keysCode.Remove(keysCode.Length - 2, 2);
                // }
                // fileTemplate = fileTemplate.Replace("#ALL_KEYS#", keysCode.ToString());
                fileTemplate = fileTemplate.Replace("#ALL_KEYS#", string.Join("\n", keysBuff));
                fileTemplate = fileTemplate.Replace("#CODE_SET#", "// todo");
                fileTemplate = fileTemplate.Replace("#CODE_GET#", "// todo");
                fileTemplate = fileTemplate.Replace("#CODE_GETORDEFAULT#", "// todo");
                fileTemplate = fileTemplate.Replace("#CODE_QUERY#", "// todo");
                fileTemplate = fileTemplate.Replace("#CODE_EXISTS#", "// todo");
                fileTemplate = fileTemplate.Replace("#CODE_REMOVE#", "// todo");

                newApiValueDef.CodeIndent(2);
                if (codeGenAttr.v2)
                {
                    // fileTemplate = fileTemplate.Replace("#NEW_API#", string.Join("\n", newApiValueDef.Comment())
                    //                                     + "\n\n"
                    //                                     + string.Join("\n", newApiClsDef.Comment()));
                    fileTemplate = fileTemplate.Replace("#NEW_API#", string.Join("\n", newApiValueDef)
                                                        + "\n\n"
                                                        + string.Join("\n", newApiClsDef));
                }

                fileTemplate = fileTemplate.Replace("#NEW_API#", "// todo");
                var codes = string.Join("\n", sourceCode);
                if (codeGenAttr.v2)
                {
                    return fileTemplate.Replace("#CODE#", "");
                }
                else
                {
                    return fileTemplate.Replace("#CODE#", codes);
                }
            }

        }

        /// <summary>
        /// 检测所有存档键标记
        /// </summary>
        private List<SaveKey> CollectSaveKeys(out Dictionary<string, ScriptInfo> scripts)
        {
            scripts = new Dictionary<string, ScriptInfo>();
            List<SaveKey> keys = new List<SaveKey>();
            var keyContainerTypes = CSharpUtil.GetTypesWithAttribute<CodeGenAttribute>(false);
            foreach (var typeKeyContainer in keyContainerTypes)
            {
                var cgAttr = typeKeyContainer.GetCustomAttribute<CodeGenAttribute>();
                if (string.IsNullOrEmpty(cgAttr.scriptName) || !cgAttr.scriptName.StartsWith("Assets/"))
                {
                    throw new Exception($"脚本路径必须以\"Assets\"开头! [{typeKeyContainer}]");
                }
                ScriptInfo scriptInfo = null;
                if (!scripts.TryGetValue(cgAttr.scriptName, out scriptInfo))
                {
                    scriptInfo = new ScriptInfo(cgAttr);
                    scripts.Add(cgAttr.scriptName, scriptInfo);
                }
                else
                {
                    throw new Exception($"CodeGenAttribute重复定义: [{cgAttr.scriptName}]");
                }
                Debug.Log($"KeyContainer --> {typeKeyContainer}");
                var fields = typeKeyContainer.GetFields(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
                var isEnum = typeKeyContainer.IsEnum;
                foreach (var field in fields)
                {
                    if (!field.IsDefined(typeof(SaveAttribute)))
                    {
                        continue;
                    }
                    var fieldType = field.FieldType;
                    if (fieldType != typeof(int) && fieldType != typeof(uint) &&
                        fieldType != typeof(long) && fieldType != typeof(ulong) &&
                        fieldType != typeof(short) && fieldType != typeof(ushort) &&
                     fieldType != typeof(string) && !fieldType.IsEnum)
                    {
                        throw new Exception($"键值类型错误({field}) 有效类型：int/uint, long/ulong, short/ushort，string，Enum");
                    }
                    var saveAttr = field.GetCustomAttribute<SaveAttribute>();
                    object fieldValue = null;
                    if (isEnum)
                    {
                        fieldValue = field.GetValue(null);
                    }
                    else
                    {
                        if (field.IsStatic && (field.IsInitOnly || field.IsLiteral))
                        {
                            fieldValue = field.GetValue(null);
                        }
                        else
                        {
                            throw new Exception($"键值必须是static类型(readonly or const)! {typeKeyContainer} {field}");
                        }
                    }
                    string numberValue = null;
                    if (fieldType.IsEnum)
                    {
                        // numberValue = Convert.ChangeType(fieldValue, fieldType.GetEnumUnderlyingType()).ToString();
                        numberValue = field.Name;
                    }
                    else
                    {
                        // numberValue = Convert.ChangeType(fieldValue, typeof(int)).ToString();
                        numberValue = fieldValue.ToString();
                    }
                    Debug.Log($"   {field} {fieldValue}={numberValue}");
                    var newKey = new SaveKey
                    {
                        keyName = field.Name,
                        keyType = fieldType,
                        keyValue = numberValue,
                        containerType = typeKeyContainer,
                        keyFieldInfo = field,
                        saveAttr = saveAttr
                    };
                    var existing = keys.Find((item) => item.keyValue == newKey.keyValue);
                    if (null != existing)
                    {
                        throw new Exception($"键值重复: {field} {existing.keyFieldInfo}");
                    }
                    existing = keys.Find((item) => item.keyName == newKey.keyName);
                    if (null != existing)
                    {
                        throw new Exception($"键名重复: {field} {existing.keyFieldInfo}");
                    }
                    scriptInfo.saveKeys.Add(newKey);
                    keys.Add(newKey);
                }
            }
            return keys;
        }

        /// <summary>
        /// 脚本文件模版
        /// </summary>
        /// <param name="si"></param>
        /// <returns></returns>
        string GetFileTemplate(ScriptInfo si)
        {
            var fp = Path.Combine(Application.dataPath, "..", "Assets/GameMain/Scripts/SaveDataUtility/CodeGenerator/Editor/CodeTemplates/ScriptFile.txt");
            return File.ReadAllText(fp, Encoding.UTF8);
        }

        /// <summary>
        /// 不带缓存的代码模版
        /// </summary>
        /// <param name="si"></param>
        /// <returns></returns>
        string GetRawSaveTemplate(ScriptInfo si)
        {
            var fp = Path.Combine(Application.dataPath, "..", "Assets/GameMain/Scripts/SaveDataUtility/CodeGenerator/Editor/CodeTemplates/WithoutCache.txt");
            return File.ReadAllText(fp, Encoding.UTF8);
        }

        /// <summary>
        /// 带缓存的代码模版
        /// </summary>
        /// <param name="si"></param>
        /// <returns></returns>
        string GetCachedSaveTemplate(ScriptInfo si)
        {
            var fp = Path.Combine(Application.dataPath, "..", "Assets/GameMain/Scripts/SaveDataUtility/CodeGenerator/Editor/CodeTemplates/WithCache.txt");
            return File.ReadAllText(fp, Encoding.UTF8);
        }

        /// <summary>
        /// 不带缓存的前缀模式代码模版
        /// </summary>
        /// <param name="si"></param>
        /// <returns></returns>
        string GetPrefixedRawSaveTemplate(ScriptInfo si)
        {
            var fp = Path.Combine(Application.dataPath, "..", "Assets/GameMain/Scripts/SaveDataUtility/CodeGenerator/Editor/CodeTemplates/PrefixedWithoutCache.txt");
            return File.ReadAllText(fp, Encoding.UTF8);
        }

        /// <summary>
        /// 带缓存的前缀模式代码模版
        /// </summary>
        /// <param name="si"></param>
        /// <returns></returns>
        string GetPrefixedCachedSaveTemplate(ScriptInfo si)
        {
            var fp = Path.Combine(Application.dataPath, "..", "Assets/GameMain/Scripts/SaveDataUtility/CodeGenerator/Editor/CodeTemplates/PrefixedWithCache.txt");
            return File.ReadAllText(fp, Encoding.UTF8);
        }

        /// <summary>
        /// 读取代码模版
        /// </summary>
        string GetCodeTemplate(string templateName)
        {
            var fp = Path.Combine(Application.dataPath, "..", $"Assets/GameMain/Scripts/SaveDataUtility/CodeGenerator/Editor/CodeTemplates/{templateName}.txt");
            return File.ReadAllText(fp, Encoding.UTF8);
        }

        void GenerateCode(ScriptInfo scriptInfo)
        {
            var rootDir = Path.GetFullPath(Path.Combine(Application.dataPath, ".."));
            var scriptFullPath = Path.Combine(rootDir, scriptInfo.codeGenAttr.scriptName);
            Debug.Log($"Code Generate: {scriptFullPath}");
            var code = scriptInfo.GetCode(this);
            // Debug.Log($"Code: \n{code}");
            File.WriteAllText(scriptFullPath, code, Encoding.UTF8);
        }

        [MenuItem("Tools/代码生成器/刷新存档代码")]
        public static void GenerateCode()
        {
            var cg = new CodeGenerator();
            var keys = cg.CollectSaveKeys(out Dictionary<string, ScriptInfo> scripts);
            foreach (var si in scripts)
            {
                cg.GenerateCode(si.Value);
            }
            AssetDatabase.Refresh();
        }
    }

    public static class CodeGenHelper
    {
        public static void ReplaceInLines(this string[] source, string replace, string target)
        {
            for (int i = source.Length - 1; i >= 0; i--)
            {
                source[i] = source[i].Replace(replace, target);
            }
        }

        public static void ReplaceInLines(this List<string> source, string replace, string target)
        {
            for (int i = source.Count - 1; i >= 0; i--)
            {
                source[i] = source[i].Replace(replace, target);
            }
        }

        public static List<string> Comment(this List<string> source)
        {
            for (int i = source.Count - 1; i >= 0; i--)
            {
                source[i] = "// " + source[i];
            }
            return source;
        }

        public static string[] SplitCodeLines(this string source)
        {
            return source.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None);
        }

        public static List<string> SplitCodeLinesToList(this string source)
        {
            return new List<string>(source.SplitCodeLines());
        }

        /// <summary>
        /// 代码缩进
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static void CodeIndent(this string[] source, int level)
        {
            var indent = new string(' ', level * 4);
            for (int i = 0; i < source.Length; i++)
            {
                if (!string.IsNullOrEmpty(source[i]))
                    source[i] = indent + source[i];
            }
        }

        /// <summary>
        /// 代码缩进
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static void CodeIndent(this List<string> source, int level)
        {
            var indent = new string(' ', level * 4);
            for (int i = 0; i < source.Count; i++)
            {
                if (!string.IsNullOrEmpty(source[i]))
                    source[i] = indent + source[i];
            }
        }
    }


    // class SaveValue<T>
    // {
    //     public T Value
    //     {
    //         get {
    //             throw new System.NotImplementedException();
    //         }
    //         set {
    //             throw new System.NotImplementedException();
    //         }
    //     }

    //     public T ValueOrDefault(T defValue)
    //     {
    //         throw new System.NotImplementedException();
    //     }

    //     public bool Query(out T value)
    //     {
    //         throw new System.NotImplementedException();
    //     }

    //     public bool Exists => false;

    //     public void Remove()
    //     {
    //     }
    // }

    // class SaveValueCached<T>
    // {
    //     T _cache;
    //     bool _loaded;

    //     public T Value
    //     {
    //         get {
    //             throw new System.NotImplementedException();
    //         }
    //         set {
    //             throw new System.NotImplementedException();
    //         }
    //     }

    //     public T ValueOrDefault(T defValue)
    //     {
    //         throw new System.NotImplementedException();
    //     }

    //     public bool Query(out T value)
    //     {
    //         throw new System.NotImplementedException();
    //     }

    //     public bool Exists => false;

    //     public void Remove()
    //     {
    //     }
    // }

    // class SaveValuePre<T, TS>
    // {
    //     public T Set(TS suffix, T value)
    //     {
    //         throw new System.NotImplementedException();
    //     }

    //     public T Value(TS suffix)
    //     {
    //         throw new System.NotImplementedException();
    //     }

    //     public T ValueOrDefault(TS suffix, T defValue)
    //     {
    //         throw new System.NotImplementedException();
    //     }

    //     public bool Query(TS suffix, out T value)
    //     {
    //         throw new System.NotImplementedException();
    //     }

    //     public bool Exists(TS suffix)
    //     {
    //         throw new System.NotImplementedException();
    //     }

    //     public void Remove(TS suffix)
    //     {
    //     }
    // }

    // class SaveValuePreCached<T, TS>
    // {
    //     System.Collections.Generic.Dictionary<TS, T> _cache;

    //     public T Set(TS suffix, T value)
    //     {
    //         throw new System.NotImplementedException();
    //     }

    //     public T Value(TS suffix)
    //     {
    //         throw new System.NotImplementedException();
    //     }

    //     public T ValueOrDefault(TS suffix, T defValue)
    //     {
    //         throw new System.NotImplementedException();
    //     }

    //     public bool Query(TS suffix, out T value)
    //     {
    //         throw new System.NotImplementedException();
    //     }

    //     public bool Exists(TS suffix)
    //     {
    //         throw new System.NotImplementedException();
    //     }

    //     public void Remove(TS suffix)
    //     {
    //     }
    // }
}
